
/**
    @fileOverview 진도 컴포넌트를 구현하기 위한 코어 클래스
    @version 1.8.0
    @since 2011. 7. 13.
**/

/**
    진도 모바일 컴포넌트를 구현하기 위한 코어 클래스.
    다른 컴포넌트가 상속받는 용도로 사용된다.

    @class jindo.m.Component
    @uses jindo.m
    @keyword component, base, core
    @group Component
    @update
    @invisible
**/
jindo.m.Component = jindo.$Class({
    /** @lends jindo.m.Component.prototype */

    _htEventHandler : null,
    _htOption : null,
    $static : {
        VERSION : "1.8.0"
    },

    /**
        jindo.m.Component를 초기화한다.
        @constructor
    **/
    $init : function() {
        var aInstance = this.constructor.getInstance();
        aInstance.push(this);
        this._htEventHandler = {};
        this._htOption = {};
        this._htOption._htSetter = {};
    },

    /**
        옵션값을 설정하거나 가져온다.
        htCustomEventHandler 옵션을 선언해서 attach() 메소드를 사용하지 않고 커스텀 이벤트핸들러를 등록할 수 있다.

        @method option
        @param {String} sName 옵션의 이름
        @param {String} sValue 옵션의 값
        @return {this} 컴포넌트 객체 자신
        @example
            var MyComponent = jindo.$Class({
                method : function() {
                    alert(this.option("foo"));
                }
            }).extend(jindo.m.Component);

            var oInst = new MyComponent();
            oInst.option("foo", 123); // 또는 oInst.option({ foo : 123 });
            oInst.method(); // 결과 123
        @example
            //커스텀이벤트핸들러 등록예제
            oInst.option("htCustomEventHandler", {
                test : function(oCustomEvent) {

                }
            });

            //이미 "htCustomEventHandler" 옵션이 설정되어있는 경우에는 무시된다.
            oInst.option("htCustomEventHandler", {
                change : function(oCustomEvent) {

                }
            });
    **/
    option : function(sName, vValue) {
        switch (typeof sName) {
            case "undefined" :
                var oOption = {};
                for(var i in this._htOption){
                    if(!(i == "htCustomEventHandler" || i == "_htSetter")){
                        oOption[i] = this._htOption[i];
                    }
                }
                return oOption;
            case "string" :
                if (typeof vValue != "undefined") {
                    if (sName == "htCustomEventHandler") {
                        if (typeof this._htOption[sName] == "undefined") {
                            this.attach(vValue);
                        } else {
                            return this;
                        }
                    }

                    this._htOption[sName] = vValue;
                    if (typeof this._htOption._htSetter[sName] == "function") {
                        this._htOption._htSetter[sName](vValue);
                    }
                } else {
                    return this._htOption[sName];
                }
                break;
            case "object" :
                for(var sKey in sName) {
                    if (sKey == "htCustomEventHandler") {
                        if (typeof this._htOption[sKey] == "undefined") {
                            this.attach(sName[sKey]);
                        } else {
                            continue;
                        }
                    }
                    if(sKey !== "_htSetter"){
                        this._htOption[sKey] = sName[sKey];
                    }

                    if (typeof this._htOption._htSetter[sKey] == "function") {
                        this._htOption._htSetter[sKey](sName[sKey]);
                    }
                }
                break;
        }
        return this;
    },

    /**
        옵션의 setter 함수를 설정하거나 가져온다.
        옵션의 setter 함수는 지정된 옵션이 변경되면 수행되는 함수이다.

        @method optionSetter
        @param {String} sName setter의 이름
        @param {Function} fSetter setter 함수
        @return {this} 컴포넌트 객체 자신
        @example
            oInst.option("sMsg", "test");
            oInst.optionSetter("sMsg", function(){
                alert("sMsg 옵션값이 변경되었습니다.");
            });
            oInst.option("sMsg", "change"); -> alert발생
        @example
            //HashTable 형태로 설정가능
            oInst.optionSetter({
                "sMsg" : function(){
                },
                "nNum" : function(){
                }
            });
    **/
    optionSetter : function(sName, fSetter) {
        switch (typeof sName) {
            case "undefined" :
                return this._htOption._htSetter;
            case "string" :
                if (typeof fSetter != "undefined") {
                    this._htOption._htSetter[sName] = jindo.$Fn(fSetter, this).bind();
                } else {
                    return this._htOption._htSetter[sName];
                }
                break;
            case "object" :
                for(var sKey in sName) {
                    this._htOption._htSetter[sKey] = jindo.$Fn(sName[sKey], this).bind();
                }
                break;
        }
        return this;
    },

    /**
        이벤트를 발생시킨다.

        @method fireEvent
        @param {String} sEvent 커스텀이벤트명
        @param {Object} oEvent 커스텀이벤트 핸들러에 전달되는 객체.
        @return {Boolean} 핸들러의 커스텀이벤트객체에서 stop메소드가 수행되면 false를 리턴
        @example
            //커스텀 이벤트를 발생시키는 예제
            var MyComponent = jindo.$Class({
                method : function() {
                    this.fireEvent('happened', {
                        sHello : 'world',
                        nAbc : 123
                    });
                }
            }).extend(jindo.m.Component);

            var oInst = new MyComponent().attach({
                happened : function(oCustomEvent) {
                    alert(oCustomEvent.sHello + '/' + oCustomEvent.nAbc); // 결과 : world/123
                }
            });

            <button onclick="oInst.method();">Click me</button>
    **/
    fireEvent : function(sEvent, oEvent) {
        oEvent = oEvent || {};
        var fInlineHandler = this['on' + sEvent],
            aHandlerList = this._htEventHandler[sEvent] || [],
            bHasInlineHandler = typeof fInlineHandler == "function",
            bHasHandlerList = aHandlerList.length > 0;

        if (!bHasInlineHandler && !bHasHandlerList) {
            return true;
        }
        aHandlerList = aHandlerList.concat(); //fireEvent수행시 핸들러 내부에서 detach되어도 최초수행시의 핸들러리스트는 모두 수행

        oEvent.sType = sEvent;
        if (typeof oEvent._aExtend == 'undefined') {
            oEvent._aExtend = [];
            oEvent.stop = function(){
                if (oEvent._aExtend.length > 0) {
                    oEvent._aExtend[oEvent._aExtend.length - 1].bCanceled = true;
                }
            };
        }
        oEvent._aExtend.push({
            sType: sEvent,
            bCanceled: false
        });

        var aArg = [oEvent],
            i, nLen;

        for (i = 2, nLen = arguments.length; i < nLen; i++){
            aArg.push(arguments[i]);
        }

        if (bHasInlineHandler) {
            fInlineHandler.apply(this, aArg);
        }

        if (bHasHandlerList) {
            var fHandler;
            for (i = 0, fHandler; (fHandler = aHandlerList[i]); i++) {
                fHandler.apply(this, aArg);
            }
        }

        return !oEvent._aExtend.pop().bCanceled;
    },

    /**
        커스텀 이벤트 핸들러를 등록한다.

        @method attach
        @param {String} sEvent 커스텀 이벤트 명
        @param {Function} fHandlerToAttach 등록 할 커스텀 이벤트 핸들러
            @param {Object} fHandlerToAttach.oCustomEvent 커스텀 이벤트 객체
        @return {this} 컴포넌트 객체 자신
        @example
            //이벤트 등록 방법 예제
            //아래처럼 등록하면 appear 라는 사용자 이벤트 핸들러는 총 3개가 등록되어 해당 이벤트를 발생시키면 각각의 핸들러 함수가 모두 실행됨.
            //attach 을 통해 등록할때는 이벤트명에 'on' 이 빠지는 것에 유의.
            function fpHandler1(oEvent) { .... };
            function fpHandler2(oEvent) { .... };

            var oInst = new MyComponent();
            oInst.onappear = fpHandler1; // 직접 등록
            oInst.attach('appear', fpHandler1); // attach 함수를 통해 등록
            oInst.attach({
                appear : fpHandler1,
                more : fpHandler2
            });
    **/
    attach : function(sEvent, fHandlerToAttach) {
        if (arguments.length == 1) {

            jindo.$H(arguments[0]).forEach(jindo.$Fn(function(fHandler, sEvent) {
                this.attach(sEvent, fHandler);
            }, this).bind());

            return this;
        }

        var aHandler = this._htEventHandler[sEvent];

        if (typeof aHandler == 'undefined'){
            aHandler = this._htEventHandler[sEvent] = [];
        }

        aHandler.push(fHandlerToAttach);

        return this;
    },

    /**
        커스텀 이벤트 핸들러를 해제한다.

        @method detach
        @param {String} sEvent 커스텀 이벤트 명
        @param {Function} fHandlerToDetach 등록 해제 할 커스텀 이벤트 핸들러
        @return {this} 컴포넌트 객체 자신
        @example
            //이벤트 해제 예제
            oInst.onappear = null; // 직접 해제
            oInst.detach('appear', fpHandler1); // detach 함수를 통해 해제
            oInst.detach({
                appear : fpHandler1,
                more : fpHandler2
            });
    **/
    detach : function(sEvent, fHandlerToDetach) {
        if (arguments.length == 1) {
            jindo.$H(arguments[0]).forEach(jindo.$Fn(function(fHandler, sEvent) {
                this.detach(sEvent, fHandler);
            }, this).bind());

            return this;
        }

        var aHandler = this._htEventHandler[sEvent];
        if (aHandler) {
            for (var i = 0, fHandler; (fHandler = aHandler[i]); i++) {
                if (fHandler === fHandlerToDetach) {
                    aHandler = aHandler.splice(i, 1);
                    break;
                }
            }
        }

        return this;
    },

    /**
        등록된 모든 커스텀 이벤트 핸들러를 해제한다.

        @method detachAll
        @param {String} sEvent 이벤트명. 생략시 모든 등록된 커스텀 이벤트 핸들러를 해제한다.
        @return {this} 컴포넌트 객체 자신
        @example
            //"show" 커스텀이벤트 핸들러 모두 해제
            oInst.detachAll("show");

            //모든 커스텀이벤트 핸들러 해제
            oInst.detachAll();
    **/
    detachAll : function(sEvent) {
        var aHandler = this._htEventHandler;

        if (arguments.length) {

            if (typeof aHandler[sEvent] == 'undefined') {
                return this;
            }

            delete aHandler[sEvent];

            return this;
        }

        for (var o in aHandler) {
            delete aHandler[o];
        }
        return this;
    }
});

/**
    다수의 컴포넌트를 일괄 생성하는 Static Method

    @method factory
    @static
    @param {Array} aObject 기준엘리먼트의 배열
    @param {HashTable} htOption 옵션객체의 배열
    @return {Array} 생성된 컴포넌트 객체 배열
    @example
        var Instance = jindo.m.Component.factory(
            cssquery('li'),
            {
                foo : 123,
                bar : 456
            }
        );
**/

jindo.m.Component.factory = function(aObject, htOption) {
    var aReturn = [],
        oInstance;

    if (typeof htOption == "undefined") {
        htOption = {};
    }

    for(var i = 0, el; (el = aObject[i]); i++) {
        oInstance = new this(el, htOption);
        aReturn[aReturn.length] = oInstance;
    }

    return aReturn;
};

/**
    컴포넌트의 생성된 인스턴스를 리턴한다.
    
    @method getInstance
    @static
    @deprecated
    
    @remark 본 메서드는 deprecated 되었으며 멀지 않은 릴리즈부터 사라질 예정입니다. 
    
    @return {Array} 생성된 인스턴스의 배열
**/
jindo.m.Component.getInstance = function(){
    if (typeof this._aInstance == "undefined") {
        this._aInstance = [];
    }
    return this._aInstance;
};